# -*- coding: utf-8 -*-
"""
Module Description: 请求上下文
"""
import copy
import threading

from app.const.lang_enum import LangEnum
import utils.json_utils as json

# 默认请求编号
from utils.time_utils import getCT

DEFAULT_TRACE_ID = 'default-trace-id'


def _trace_id():
    """
    创建唯一请求跟踪id
    为了与正常trace_id的无'-'格式区分,这里使用带'-'的uuid
    """
    import uuid
    return uuid.uuid1().__str__()


class ActionContext(object):
    """
    请求上下文
    request-scope context
    """
    def __init__(self, action_id,
                 trace_id=None,
                 rid=None,
                 ver=None,
                 lang=LangEnum.Simplified,
                 app_key='',
                 retail_id=None,
                 ip='',
                 activity_id='',
                 progress_list=[]):
        """
        :param trace_id: 此次请求的跟踪id
        :param action_id: 接口编号
        :param rid: 玩家编号
        :param ver: 版本号
        """
        self.trace_id = trace_id or _trace_id()
        self.action_id = int(action_id)
        # self.server_id = server_id if server_id == 'cross' else int(server_id)
        # self.app_id = int(app_id)
        self.app_key = app_key
        # self.retail_id = retail_id

        self.rid = rid
        self.ver = ver
        self.lang = lang
        self.ip = ip
        self.activity_id = activity_id
        self.progress_list = progress_list

        self.current_time = getCT()
        # 请求对象, 可空
        self.request = None
        # 响应对象, 可空
        self.response = None
        # 增加role_data，可空，供登录使用
        self.role_data = None
        # 增加ServerInfo，可空，供登录使用
        self.server_info_dict = {}

    def __str__(self):
        return json.dumps({
            'trace_id': self.trace_id,
            'action_id': self.action_id,
            # 'server_id': self.server_id,
            # 'app_id': self.app_id,
            'app_key': self.app_key,
            # 'retail_id': self.retail_id,
            'rid': self.rid,
            'ver': self.ver,
            'lang': self.lang,
        })

    @classmethod
    def create_by_arguments(cls, args):
        """
        传入的请求参数
        :param args: type(dict)
        :return:
        """
        # 如果没有传入，则自己生成
        trace_id = args.get('traceid', _trace_id())

        action_id = args.get('actionid')
        server_id = args.get('serverid')
        rid = args.get('rid')
        ver = args.get('ver')
        lang = args.get('lang')
        app_key = args.get('appkey', '')
        retail_id = args.get('retailid')
        # TODO
        app_id = args.get('appid', 0)
        logical_region_id = args.get('logicalregionid', 0)
        activity_id = args.get('activity_id', '')
        progress_list = args.get('progress_list', [])

        ctx = ActionContext(trace_id=trace_id,
                            action_id=action_id,
                            server_id=server_id,
                            app_id=(int(app_id) or int(logical_region_id)),
                            rid=rid,
                            ver=ver,
                            lang=lang,
                            app_key=app_key,
                            retail_id=retail_id,
                            activity_id=activity_id,
                            progress_list=progress_list)
        return ctx

    @classmethod
    def create_by_request_response(cls, request, response):
        """
        根据请求对象创建上下文对象
        :param request: 请求对象
        :param response: 响应对象
        :return: ctx
        """
        # 如果没有传入，则自己生成
        trace_id = request.get_attribute('traceid', default=_trace_id())

        action_id = request.action_id
        server_id = request.get_attribute('serverid')
        rid = request.get_attribute('rid')
        ver = request.get_attribute('ver')
        lang = request.get_attribute('lang')
        app_key = request.get_attribute('appkey')
        retail_id = request.get_attribute('retailid')
        client_ip = request.get_attribute('ip', '')
        activity_id = request.get_attribute('activityid', '')
        progress_list = request.get_attribute('progress_list', [])
        # TODO
        # app_id = request.get_attribute('appid', default=0)
        logical_region_id = request.get_attribute('logicalregionid')

        ctx = ActionContext(trace_id=trace_id,
                            action_id=action_id,
                            # server_id=server_id,
                            # app_id=(int(app_id) or int(logical_region_id)),
                            rid=rid,
                            ver=ver,
                            lang=lang,
                            app_key=app_key,
                            retail_id=retail_id,
                            ip=client_ip,
                            activity_id=activity_id,
                            progress_list=progress_list)
        ctx.request = request
        ctx.response = response
        return ctx

    def __deepcopy__(self, memodict=None):
        new_ctx = ActionContext(
            trace_id=self.trace_id,
            action_id=self.action_id,
            # server_id=self.server_id,
            # app_id=self.app_id,
            rid=self.rid,
            ver=self.ver,
            lang=self.lang,
            app_key=self.app_key,
            # retail_id=self.retail_id,
            activity_id=self.activity_id,
            progress_list=self.progress_list
        )
        new_ctx.request = self.request
        new_ctx.response = self.response
        return new_ctx

    def __enter__(self):
        self.register_storage()

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.logout_storage()

    def register_storage(self):
        """
        将上下文对象注册到ActionContextStorage中
        """
        action_context_storage.push_ctx(self)

    def logout_storage(self):
        """
        将上下文对象从ActionContextStorage中注销
        """
        action_context_storage.pop_ctx(spec_ctx=self)


class _ActionContextStorage(object):
    """
    请求上下文存储
    此类仅是作为占位符,提供嵌套上下文等特性,具体的上下文数据还是放在ActionContext类中

    嵌套上下文示例:
    >>> with ActionContext(action_id=1, server_id=1, app_id=1010) as ctx1:
    >>>     # 此处使用ctx1上下文
    >>>     with ActionContext(action_id=1, server_id=2, app_id=1010) as ctx2:
    >>>         # 此处使用ctx2上下文
    >>>     # 此处使用ctx1上下文
    """
    # 上下文映射
    # {thread_id: ctx_stack}
    _ctx_mapping = {}

    @property
    def ctx(self):
        return self.ctx_stack[-1] if len(self.ctx_stack) else None

    @property
    def stack_idx(self):
        return threading.currentThread().ident

    @property
    def ctx_stack(self):
        if self.stack_idx not in self._ctx_mapping:
            self._ctx_mapping[self.stack_idx] = []
        return self._ctx_mapping[self.stack_idx]

    def push_ctx(self, ctx):
        """
        向上下文中压入最新的上下文
        :param ctx: 上下文对象
        """
        self.ctx_stack.append(ctx)

    def pop_ctx(self, spec_ctx=None):
        """
        从上下文栈中弹出最新/指定的上下文
        :param spec_ctx: 指定的上下文对象
        """
        if spec_ctx and spec_ctx is not self.ctx:
            raise RuntimeError('Not current context:cur_ctx={}, ctxstack_trarceid={}'.format(self.ctx.trace_id,
                                                                                             str([ctx.trace_id for ctx in self.ctx_stack])))
        self.ctx_stack.pop()

        if not self.ctx_stack:
            del self._ctx_mapping[self.stack_idx]

    def destory(self):
        """销毁当前线程的上下文栈"""
        cur_ctx_stack = self.ctx_stack
        while len(cur_ctx_stack):
            cur_ctx_stack.pop()
        del self._ctx_mapping[self.stack_idx]


def temporary_ctx(app_id=None, server_id=None, activity_id=None):
    """
    根据当前上下文创建具有新参数的上下文对象

    >>> with temporary_ctx(server_id=3):
    >>>     pass # do something

    :param activity_id: 新的活动id，可选
    :param app_id: 新的应用编号,可选
    :param server_id: 新的区服编号,可选
    :return: ActionContext obj
    """
    # app_id,server_id至少传一个
    if app_id is None and server_id is None:
        raise ValueError('Please do something significant.')

    if action_context_storage.ctx is None:
        raise RuntimeError('Empty context stack now!')

    temp_ctx = copy.deepcopy(action_context_storage.ctx)
    temp_ctx.app_id = app_id if app_id is not None else temp_ctx.app_id
    temp_ctx.server_id = server_id if server_id is not None else temp_ctx.server_id
    temp_ctx.activity_id = activity_id if activity_id else temp_ctx.activity_id

    return temp_ctx


def get_trace_id(create_if_not_found=True):
    """
    获取当前请求的追踪id
    :param create_if_not_found: 如果没有从当前上下文找到trace_id,是否生成新的trace_id返回
    """
    if action_context_storage.ctx is not None:
        return action_context_storage.ctx.trace_id
    if create_if_not_found:
        return _trace_id()


action_context_storage = _ActionContextStorage()
